package FlashServer;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.LinkedList;

import flex.messaging.io.SerializationContext;
import flex.messaging.io.amf.Amf3Input;
import flex.messaging.io.amf.Amf3Output;

public class SocketHandler extends Thread 
{
	private LinkedList<SocketHandler> FUsersList = null;
	private Socket FSocket = null;
	//Input
	private Amf3Input FAMFInput;
	//Output
	private OutputStream FOutput;
	private ByteArrayOutputStream FByteBuffer;
	private Amf3Output FAMFOutput;		
	private String FUserName = "";
	private ThreadPool FPool;
	private MessageProcessor FProcessor;	
	
	private final Object lock = new Object();

	public SocketHandler(ThreadPool APool,	LinkedList<SocketHandler> AList) 
	{
		FPool = APool;
		FUsersList = AList;
		FProcessor = new MessageProcessor(this);
	}

	public void ProcessSocket(Socket ASocket) throws IOException
	{
		System.out.println("Requesting socket...");
		
		FSocket = ASocket;
		
		try
		{				
			FAMFInput = new Amf3Input(SerializationContext.getSerializationContext());
			FAMFInput.setInputStream(FSocket.getInputStream());	
		}
		catch(Exception e)
		{
			System.out.println("AMF input Error: " + e.toString());
		}
		
		try
		{				
			FOutput = FSocket.getOutputStream();			
		}
		catch(Exception e)
		{
			System.out.println("Output stream Error: " + e.toString());
		}
		
		try
		{
			FAMFOutput = new Amf3Output(SerializationContext.getSerializationContext());
		}
		catch(Exception e)
		{
			System.out.println("AMF output Error: " + e.toString());
		}
		
		synchronized (lock) 
		{
			lock.notify();
		}
	}

	public String getUserName() 
	{
		return FUserName;
	}

	public void run() 
	{
		while (true) 
		{			
			synchronized (lock) 
			{
				try 
				{					
					lock.wait();
				} 
				catch (InterruptedException ex) 
				{
					ex.printStackTrace();
				}

				System.out.println(getName() + " activated");
				
				try 
				{
					System.out.println("Incoming connection from IP: " + FSocket.getInetAddress());
					//First message is handshake, process it					
					Handshake();
					
					while (true) 
					{
						try 
						{
							//Processing input message
							ProcessMessage();
						} 
						catch (Exception ex) 
						{
							System.out.println("Ecxeption in RUN in " + getName() + " from " + FUserName);
							CloseSocket();											
							break;							
						}
					}
					//after breaking while(true) cycle
					if(!FSocket.isClosed())
					{
						CloseSocket();
					}
					FPool.ReturnToPool(this);
				} 
				catch (Exception ex) 
				{
					if(!FSocket.isClosed())
					{
						try 
						{						
							CloseSocket();						
						} 
						catch (IOException e) 
						{					
							e.printStackTrace();
						}
					}
					FPool.ReturnToPool(this);					
				}
			}
		}
	}
	
	private DataMessage ReadMessage() throws ClassNotFoundException, IOException
	{
		DataMessage vMessage = null;
		vMessage = (DataMessage)FAMFInput.readObject();	
		return vMessage;	
	}
	
	public void Send(DataMessage AMessage) throws IOException
	{
		FByteBuffer = new ByteArrayOutputStream();
		OutputStream vOutput = getOutput();
		
		Amf3Output vAMF3Out = getAMFOutput();
		vAMF3Out.setOutputStream(FByteBuffer);
		vAMF3Out.writeObject(AMessage);			
		vOutput.write(FByteBuffer.toByteArray());
		vOutput.flush();
	}
	
	public void SendToAll(DataMessage AMessage) throws IOException
	{
		synchronized (FUsersList) 
		{
			for (SocketHandler vHandler : FUsersList) 
			{
				vHandler.Send(AMessage);				
			}
		}
	}
	
	private void Handshake() throws IOException, ClassNotFoundException
	{	
		String vNewUserName = ReadMessage().Message;
				
		if(getHandlerByName(vNewUserName) != null)
		{			
			DataMessage vConnectionRejectMessage = new DataMessage();
			vConnectionRejectMessage.DataType = MessageID.ConnectionRejected;
			vConnectionRejectMessage.Message = "Duplicate user name: " + vNewUserName;			
			System.out.println(vConnectionRejectMessage.Message);
			Send(vConnectionRejectMessage);
			CloseSocket();
		}
		else
		{
			FUserName = vNewUserName;
			FUsersList.add(this);
			DataMessage vNewClientMessage = new DataMessage();
			vNewClientMessage.DataType = MessageID.NewClient;
			vNewClientMessage.Message = FUserName;			
			SendToAll(vNewClientMessage);			
			System.out.println("User added: " + vNewUserName);
		}
	}
	
	private void ProcessMessage() throws ClassNotFoundException, IOException
	{
		DataMessage vMessage = ReadMessage();
		FProcessor.Process(vMessage);
	}
	
	public SocketHandler getHandlerByName(String AName) 
	{
		synchronized (FUsersList) 
		{
			for (SocketHandler socketHandler : FUsersList) 
			{
				try 
				{
					if (socketHandler.getUserName().equals(AName))
						return socketHandler;
				} 
				catch (Exception ex) 
				{}
			}
			return null;
		}
	}

	private void CloseSocket() throws IOException
	{
		FUsersList.remove(this);
		FProcessor.DoOnClose();
		//Closing		
		try 
		{
			FAMFInput.close();
			FAMFOutput.close();
			FOutput.close();
			FByteBuffer.close();
			FSocket.close();
		}
		catch (Exception e)
		{
			System.out.println("Ecxeption in Closing from " + FUserName);
		}		
		FUserName = "";
	}
		
	public OutputStream getOutput() 
	{
		return FOutput;
	}
	
	public Amf3Output getAMFOutput() 
	{
		return FAMFOutput;
	}

	public MessageProcessor getMessageProcessor()
	{
		return FProcessor;
	}
		
}
